白话Java NIO

目前Java支持的IO共有三种:

名称 支持版本 特点
IO(也叫BIO) JDK 1.0 B=block,阻塞
NIO JDK 1.4 N=new,非阻塞
AIO(NIO2.0) JDK 1.7 A=asynchronous,异步

本文主要通过对比BIO和NIO,尽量通俗易懂的介绍NIO,重在有个整体认识,不深入代码实现,也不讨论同步/异步(因为还没弄懂-_-!。。懂了再补)。

IO

I/O,即input/output,可以说任何端到端的通信,大体都由三部分组成:输入–>传输–>输出。常见的有文件读写、网络通信,都属于IO。

BIO

概念

BIO大家都熟悉,在BIO中:
BIO

  1. 每一个端到端的连接叫Stream,两端的输入输出都是对着Stream进行的
  2. 输入输出面向的是字节(即操作实体、基本单位),每次读写操作都是一个字节一个字节进行的
  3. 从Stream中读数据,是阻塞的,即没有数据时,进行读操作的线程就进入阻塞状态,干不了其他事,直至读到数据为止

应用

以常见的Socket服务器为例:
BIO-Server

  1. 因为监听客户端连接请求的Socket会阻塞,所有单独放在一个线程,无限循环监听
  2. 因为每个客户端连接中使用的Stream读写时会阻塞,又不能相互影响,所以每收到一个连接请求,就单独开一个线程,一对一的负责客户端输入输出

弊端

  1. 阻塞造成的:如果连接较多,而且每个连接(Stream)上的数据量都很小,那么大多数的线程,大部分时间都是处于阻塞状态的(因为没有数据它要干等着啊),所以CPU大部分时间都在忙于线程调度,效率低下
  2. 面向字节造成的:因为每次读写都是一个字节,不仅慢,而且没有任何其他操作的余地,比如数据前后移动、定位等(但这个是可以通过字节数组手动改善的,即每次读写一个字节数组)

适用场景

只要反着上边说的情况,就是它的适用场景:

  • 连接数少,但每个连接上,数据量大

NIO

概念

针对BIO的缺点,在JDK 1.4推出了NIO,主打非阻塞:
NIO

BIO ——————> NIO
Stream ——————> Channel
Byte ——————> Buffer
阻塞 ——————> 阻塞/非阻塞
接头人Stream ——————> 接头人Selector
  1. 每一个端到端的连接,从Stream变成了Channel
  2. 输入输出面向的基本单位,从字节变成了缓冲区(本质就是一个封装好的字节数组)
  3. Channel有阻塞/非阻塞两种模式可选,非阻塞模式下,没有数据时就不用干等着了
  4. 不干等着,又没数据,那能干嘛呢?那就去看看其他Channel有没有数据呗?为了配合Channel的非阻塞模式,新添加了一个Selector,Selector到Channel是一对多的关系,相当于是Channel的管理者。以前数据向Stream要,现在向Selector要,Selector会把已经有数据的Channel挑选准备好,只有所有Channel都没数据时,在Selector这里才会阻塞。所以等于把阻塞的级别提高了一个维度,大大降低了阻塞的可能。可以类比进程和线程的关系:当且仅当一个进程中所有线程都死了,这个进程才算死了。

应用

还以常见的Socket服务器为例:
NIO-Server

  1. 监听客户端连接请求的Socket变成了ServerSocketChannel,设置成非阻塞模式,然后把它交给Selector来管理,并告诉Selector它只负责连接请求,不负责读写,因为不阻塞,所以不用单独开线程
  2. 无限循环,阻塞式地向Selector索要有数据的Channel:
    a. 如果是连接请求来了,那就把新的连接SocketChannel也交给Selector来管理,并告诉Selector它是负责读写数据的
    b. 如果是需要读写的数据来了(即已有的连接),那就进行数据读写处理
    c. 如果当前所有的Channel都没数据,那就在Seletor这里阻塞着,直到至少一个Channel有数据为止
    d. 虽然在Seletor这里也有可能阻塞,但仍不需要单独开线程,就在主线程办。为什么呢?如果Selector阻塞了,说明当前没有什么数据可处理的,都没事干了,主线程阻塞歇着又有何妨呢?

弊端

  1. 排队造成的:什么是排队呢?当多个Channel都有数据时,只有单个线程通过Selector顺序处理它们,不就等于在排队处理吗?那么,如果每个Channel数据量都很大,零并发,效率低下
  2. 以上问题,可以通过增加Selector数量,减小单个Selector管理的Channel量,增加并发度来缓解,但仍存在排队问题,并没有根本解决

适用场景

● 连接数多,但每个连接上,数据量小

这时你可能也发现了,那如果连接数多,每个连接上数据量也大时,以上两个就都不适用了?怎么办呢?——AIO,具体还不懂,就不多说了。